{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "8d3e3a9c",
   "metadata": {},
   "source": [
    "# Create and use functions"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bfe1aea0",
   "metadata": {},
   "source": [
    "Functions are the basic building blocks of MLRun. They are essentially Python objects that know how to run locally or on a Kubernetes cluster. This section covers how to create and customize an MLRun function, as well as common parameters across all functions."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3d68f026",
   "metadata": {},
   "source": [
    "**In this section:**\n",
    "- [**Functions overview**](#functions-overview)\n",
    "- [**Functions and projects**](#functions-and-projects)\n",
    "- [**Creating functions**](#creating-functions)\n",
    "- [**Customizing functions**](#customizing-functions)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "392e01ae",
   "metadata": {},
   "source": [
    "<a id=\"functions-overview\"></a>\n",
    "## Functions overview"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "98df9683",
   "metadata": {},
   "source": [
    "MLRun functions are used to run jobs, deploy models, create pipelines, and more. There are various kinds of MLRun functions with different capabilities, however, there are commonalities across all functions. In general, an MLRun function looks like the following:\n",
    "\n",
    "![MLRun Function](../_static/images/mlrun_function_diagram.png)\n",
    "\n",
    "You can read more about MLRun Functions [**here**](./functions.html). Each parameter and capability are explained in more detail in the following sections [**Creating functions**](#creating-functions) and [**Customizing functions**](#customizing-functions)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6b172551",
   "metadata": {},
   "source": [
    "<a id=\"functions-and-projects\"></a>\n",
    "## Functions and projects"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fe29e3d0",
   "metadata": {},
   "source": [
    "Function are members of an [**MLRun project**](../projects/project.html). Once you register a function within a project, you can execute it in your local environment or at scale on a Kubernetes cluster.\n",
    "\n",
    "The relationship between [**functions**](./functions.html), [**workflows**](../projects/build-run-workflows-pipelines.html), and [**projects**](../projects/project.html) is as follows:\n",
    "\n",
    "![MLRun Function](../_static/images/mlrun_concepts_architecture.png)\n",
    "\n",
    "After the MLRun functions and workflows are created and [**registered into the project**](../projects/create-project.html), they are invoked using the project object. This workflow pairs especially well with Git and [**CI/CD**](../projects/ci-integration.html) integration."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cd31f4a3",
   "metadata": {},
   "source": [
    "<a id=\"creating-functions\"></a>\n",
    "## Creating functions"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bada099b",
   "metadata": {},
   "source": [
    "The recommended way to create an MLRun function is by using an MLRun project (see [**create and use projects**](../projects/create-project.html)).\n",
    "The general flow looks like the following:\n",
    "\n",
    "```python\n",
    "project = mlrun.get_or_create_project(...)\n",
    "\n",
    "fn = project.set_function(...)\n",
    "```\n",
    "\n",
    "When creating a function, there are 3 main scenarios:\n",
    "1. [**Single source file**](#single-source-file) &mdash; when your code can be contained in a single file\n",
    "2. [**Multiple source files**](#multiple-source-files) &mdash; when your code requires additional files or dependencies\n",
    "3. [**Import existing function**](#import-existing-function) &mdash; when your function already exists elsewhere and you just want to import it\n",
    "\n",
    "``` {admonition} Note\n",
    "Using the `set_function` method of an MLRun project allows for each of these scenarios in a transparent way. Depending on the source passed in, the project registers the function using some lower level functions. For specific use cases, you also have access to the lower level functions {py:func}`~mlrun.run.new_function`, {py:func}`~mlrun.run.code_to_function`, and {py:func}`~mlrun.run.import_function`.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8ab1e780",
   "metadata": {},
   "source": [
    "<a id=\"using-set_function\"></a>\n",
    "### Using set_function"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c878374d",
   "metadata": {},
   "source": [
    "The MLRun project object has a method called {py:meth}`~mlrun.projects.MlrunProject.set_function`, which is a one-size-fits-all way of creating an MLRun function. This method accepts a variety of sources including Python files, Jupyter Notebooks, Git repos, and more.\n",
    "\n",
    "``` {admonition} Note\n",
    "The return value of `set_function` is your MLRun function. You can immediately run it or apply additional configurations like resources, scaling, etc. See [**Customizing functions**](#customizing-functions) for more details.\n",
    "```\n",
    "\n",
    "When using `set_function` there are a number of common parameters across all function types and creation scenarios. Consider the following example:\n",
    "\n",
    "```python\n",
    "fn = project.set_function(\n",
    "    name=\"my-function\", tag=\"latest\", func=\"my_function.py\",\n",
    "    image=\"mlrun/mlrun\", kind=\"job\", handler=\"train_model\",\n",
    "    requirements=[\"pandas==1.3.5\"], with_repo=True\n",
    ")\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bb9cebf9",
   "metadata": {},
   "source": [
    "- **name:** Name of your MLRun function within the given project. This is displayed in the MLRun UI, as well as the Kubernetes pod.\n",
    "\n",
    "- **tag:** Tag for your function (much like a Docker image). Omitting this parameter defaults to `latest`.\n",
    "\n",
    "- **func:** What to run with the MLRun function. This can be a number of things including files (`.py`, `.ipynb`, `.yaml`, etc.), URIs (`hub://` prefixed Function Hub URI, `db://` prefixed MLRun DB URI), existing MLRun function objects, or `None` (for current `.ipynb` file).\n",
    "\n",
    "- **image:** Docker image to use when containerizing the piece of code. If you also specify the `requirements` parameter to build a new Docker image, the `image` parameter is used as the base image.\n",
    "\n",
    "- **kind:** Runtime the MLRun function uses. See [**Kinds of functions (runtimes)**](../concepts/functions-overview.html) for the list of supported batch and real-time runtimes.\n",
    "\n",
    "- **handler:** Default function handler to invoke (e.g. a Python function within your script). This handler can also be overridden when executing the function.\n",
    "\n",
    "- **requirements:** Additional Python dependencies needed for the function to run. Using this parameter results in a new Docker image (using the `image` parameter as a base image). This can be a list of Python dependencies or a path to a `requirements.txt` file.\n",
    "\n",
    "- **with_repo:** Set to `True` if the function requires additional files or dependencies within a Git repo or archive file. This Git repo or archive file is specified on a project level via `project.set_source(...)`, which the function consumes. If this parameter is omitted, the default is `False`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "edeffa8d",
   "metadata": {},
   "source": [
    "<a id=\"building-images\"></a>\n",
    "### Building images"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "954c7f23",
   "metadata": {},
   "source": [
    "If your MLRun function requires additional libraries or files, you might need to build a new Docker image. You can do this by specifying a base image to use as the `image`, your requirements via `requirements`, and (optionally) your source code via `with_repo=True` (where the source is specified by `project.set_source(...)`). See [**Build function image**](./image-build.html) for more information on when a build is required.\n",
    "\n",
    "``` {admonition} Note\n",
    "When using `with_repo`, the contents of the Git repo or archive are available in the current working directory of your MLRun function during runtime.\n",
    "```\n",
    "\n",
    "A good place to start is one of the default MLRun images:\n",
    "- `mlrun/mlrun`: Suits most lightweight components (includes `sklearn`, `pandas`, `numpy` and more)\n",
    "- `mlrun/ml-models`: Suits most CPU ML/DL workloads (includes `Tensorflow`, `Keras`, `PyTorch` and more)\n",
    "- `mlrun/ml-models-gpu`: Suits most GPU ML/DL workloads (includes GPU `Tensorflow`, `Keras`, `PyTorch` and more)\n",
    "\n",
    "Dockerfiles for the MLRun images can be found [**here**](https://github.com/mlrun/mlrun/tree/development/dockerfiles)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8e827478",
   "metadata": {},
   "source": [
    "<a id=\"single-source-file\"></a>\n",
    "### Single source file"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "be147543",
   "metadata": {},
   "source": [
    "The simplest way to create a function is to use a single file as the source. The code itself is embedded into the MLRun function object. This makes the function quite portable since it does not depend on any external files. You can use any source file supported by MLRun such as Python or Jupyter notebook.\n",
    "``` {admonition} Note\n",
    "MLRun is not limited to Python. Files of type Bash, Go, etc. are also supported.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "41f62728",
   "metadata": {},
   "source": [
    "#### Python"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bf09c489",
   "metadata": {},
   "source": [
    "This is the simplest way to create a function out of a given piece of code. Simply pass in the path to the Python file *relative to your project context directory*."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "af00fedd",
   "metadata": {},
   "source": [
    "```python\n",
    "fn = project.set_function(\n",
    "    name=\"python\", func=\"job.py\",  kind=\"job\",\n",
    "    image=\"mlrun/mlrun\", handler=\"handler\"\n",
    ")\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5ab01939",
   "metadata": {},
   "source": [
    "#### Jupyter Notebook"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d5c252fd",
   "metadata": {},
   "source": [
    "This is a great way to create a function out of a Jupyter Notebook. Just pass in the path to the Jupyter Notebook  *relative to your project context directory*. You can use [**MLRun cell tags**](./mlrun_code_annotations.html) to specify which parts of the notebook should be included in the function. \n",
    "\n",
    "``` {admonition} Note\n",
    "To ensure that the latest changes are included, make sure you save your notebook before creating/updating the function.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2c934acd",
   "metadata": {},
   "source": [
    "```python\n",
    "fn = project.set_function(\n",
    "    name=\"notebook\", func=\"nb.ipynb\", kind=\"serving\", \n",
    "    image=\"mlrun/ml-models\", requirements=[\"pandas==1.3.5\"]\n",
    ")\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6b36faf7",
   "metadata": {},
   "source": [
    "You can also create an MLRun function out of the current Jupyter Notebook you are running in. To do this, simply ommit the `func` parameter in `set_function`. "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0204fd5d",
   "metadata": {},
   "source": [
    "<a id=\"multiple-source-files\"></a>\n",
    "### Multiple source files"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4f32f1ea",
   "metadata": {},
   "source": [
    "If your code requires additional files or external libraries, you need to use a source that supports multiple files such as Git, an archive (zip/tar), or V3IO file share. This approach (especially using a Git repo) pairs well with MLRun projects.\n",
    "\n",
    "To do this, you must:\n",
    "- Provide `with_repo=True` when creating your function via `project.set_function(...)`\n",
    "- Set project source via `project.set_source(source=...)`\n",
    "\n",
    "This instructs MLRun to load source code from the git repo/archive/file share associated with the project. There are two ways to load these additional files:\n",
    "\n",
    "#### Load code from container\n",
    "The function is built once. *This is the preferred approach for production workloads*. For example:\n",
    "```python\n",
    "project.set_source(source=\"git://github.com/mlrun/project-archive.git\")\n",
    "\n",
    "fn = project.set_function(\n",
    "    name=\"myjob\", handler=\"job_func.job_handler\",\n",
    "    image=\"mlrun/mlrun\", kind=\"job\", with_repo=True,\n",
    ")\n",
    "\n",
    "project.build_function(fn)\n",
    "```\n",
    "\n",
    "#### Load code at runtime\n",
    "The function pulls the source code at runtime. *This is a simpler approach during development that allows for making code changes without re-building the image each time*. For example:\n",
    "\n",
    "```python\n",
    "archive_url = \"https://s3.us-east-1.wasabisys.com/iguazio/project-archive/project-archive.zip\"\n",
    "project.set_source(source=archive_url, pull_at_runtime=True)\n",
    "\n",
    "fn = project.set_function(\n",
    "    name=\"nuclio\", handler=\"nuclio_func:nuclio_handler\",\n",
    "    image=\"mlrun/mlrun\", kind=\"nuclio\", with_repo=True,\n",
    ")\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "969cc92e",
   "metadata": {},
   "source": [
    "<a id=\"import-existing-function\"></a>\n",
    "### Import or use an existing function"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0c5adbe0",
   "metadata": {},
   "source": [
    "If you already have an MLRun function that you want to import, you can do so from multiple locations such as YAML, Function Hub, and MLRun DB."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "36390042",
   "metadata": {},
   "source": [
    "#### YAML"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ed7f8c84",
   "metadata": {},
   "source": [
    "MLRun functions can be exported to YAML files via `fn.export()`. These YAML files can then be imported via the following:"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7d5f5e26",
   "metadata": {},
   "source": [
    "```python\n",
    "fn = project.set_function(name=\"import\", func=\"function.yaml\")\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "94ffa4b2",
   "metadata": {},
   "source": [
    "#### Function Hub"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8a65c196",
   "metadata": {},
   "source": [
    "Functions can also be imported from the [**MLRun Function Hub**](https://www.mlrun.org/hub): simply import using the name of the function and the `hub://` prefix:\n",
    "``` {admonition} Note\n",
    "By default, the `hub://` prefix points to the MLRun Function Hub. You can substitute your own repo. See {ref}`git-repo-as-hub`.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "89e8a5ad",
   "metadata": {},
   "source": [
    "```python\n",
    "fn = project.set_function(name=\"describe\", func=\"hub://describe\")\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fd2d5a82",
   "metadata": {},
   "source": [
    "#### MLRun DB"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "66397b78",
   "metadata": {},
   "source": [
    "You can also import functions directly from the MLRun DB. These could be functions that have not been pushed to a git repo, archive, or Function Hub. Import via the name of the function and the `db://` prefix:"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a69e3f7f",
   "metadata": {},
   "source": [
    "```python\n",
    "fn = project.set_function(name=\"db\", func=\"db://import\")\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "58c92849",
   "metadata": {},
   "source": [
    "#### MLRun function"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "69c0d1c5",
   "metadata": {},
   "source": [
    "You can also directly use an existing MLRun function object. This is usually used when more granular control over function parameters is required (e.g. advanced parameters that are not supported by {py:meth}`~mlrun.projects.MlrunProject.set_function`).\n",
    "\n",
    "This example uses a {ref}`real-time serving pipeline (graph) <serving-graph>`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7ceb2812",
   "metadata": {},
   "source": [
    "```python\n",
    "fn = mlrun.new_function(\"serving\", kind=\"serving\", image=\"mlrun/mlrun\")\n",
    "graph = serving.set_topology(\"flow\")\n",
    "graph.to(name=\"double\", handler=\"mylib.double\") \\\n",
    "     .to(name=\"add3\", handler=\"mylib.add3\") \\\n",
    "     .to(name=\"echo\", handler=\"mylib.echo\").respond()\n",
    "\n",
    "project.set_function(name=\"serving\", func=fn, with_repo=True)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0b2601c1",
   "metadata": {},
   "source": [
    "<a id=\"customizing-functions\"></a>\n",
    "## Customizing functions"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6bed5e82",
   "metadata": {},
   "source": [
    "Once you have created your MLRun function, there are many customizations you can add. Some potential customizations include:\n",
    "\n",
    "### Environment variables\n",
    "Environment variables can be added individually, from a Python dictionary, or a file:\n",
    "\n",
    "```python\n",
    "# Single variable\n",
    "fn.set_env(name=\"MY_ENV\", value=\"MY_VAL\")\n",
    "\n",
    "# Multiple variables\n",
    "fn.set_envs(env_vars={\"MY_ENV\" : \"MY_VAL\", \"SECOND_ENV\" : \"SECOND_VAL\"})\n",
    "\n",
    "# Multiple variables from file\n",
    "fn.set_envs(file_path=\"env.txt\")\n",
    "```\n",
    "\n",
    "### Memory, CPU, GPU resources\n",
    "Adding requests and limits to your function specify what compute resources are required. It is best practice to define this for each MLRun function. See [**CPU, GPU, and memory limits for user jobs**](./configuring-job-resources.html#cpu-gpu-and-memory-limits-for-user-jobs) for more information on configuring resources.\n",
    "\n",
    "\n",
    "```python\n",
    "# Requests - lower bound\n",
    "fn.with_requests(mem=\"1G\", cpu=1)\n",
    "\n",
    "# Limits - upper bound\n",
    "fn.with_limits(mem=\"2G\", cpu=2, gpus=1)\n",
    "```\n",
    "\n",
    "### Scaling and auto-scaling\n",
    "Scaling behavior can be added to real-time and distributed runtimes including `nuclio`, `serving`, `spark`, `dask`, and `mpijob`. See [**Replicas**](./configuring-job-resources.html#replicas) to see how to configure scaling behavior per runtime. This example demonstrates setting replicas for `nuclio`/`serving` runtimes:\n",
    "\n",
    "```python\n",
    "# Nuclio/serving scaling\n",
    "fn.spec.replicas = 2\n",
    "fn.spec.min_replicas = 1\n",
    "fn.spec.max_replicas = 4\n",
    "```\n",
    "\n",
    "### Mount persistent storage\n",
    "In some instances, you might need to mount a file-system to your container to persist data. This can be done with native K8s PVC's or the V3IO data layer for Iguazio clusters. See [**Attach storage to functions**](./function-storage.html) for more information on the storage options.\n",
    "\n",
    "```python\n",
    "# Mount persistent storage - V3IO\n",
    "fn.apply(mlrun.mount_v3io())\n",
    "\n",
    "# Mount persistent storage - PVC\n",
    "fn.apply(mlrun.platforms.mount_pvc(pvc_name=\"data-claim\", volume_name=\"data\", volume_mount_path=\"/data\"))\n",
    "```\n",
    "\n",
    "### Node selection\n",
    "Node selection can be used to specify where to run workloads (e.g. specific node groups, instance types, etc.). This is a more advanced parameter mainly used in production deployments to isolate platform services from workloads. See [**Node affinity**](../concepts/node-affinity.html) for more information on how to configure node selection.\n",
    "\n",
    "```python\n",
    "# Only run on non-spot instances\n",
    "fn.with_node_selection(node_selector={\"app.iguazio.com/lifecycle\" : \"non-preemptible\"})\n",
    "```"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.6 (main, Nov 14 2022, 16:10:14) [GCC 11.3.0]"
  },
  "vscode": {
   "interpreter": {
    "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
